﻿using Moki;
using Moki.Tests.Model;
using NUnit.Framework;
using System.Collections;
using System;

namespace Moki.Specification
{
    [TestFixture]
    public class ConsecutiveCallsSpec
    {
        [TearDown]
        public void TearDown()
        {
            Mock.CleanUp();
        }

        [Test]
        public void Next_return_should_return_consecutive_values()
        {
            IGPS gps = Mock.Create<IGPS>();

            Mock.WhenCalled(() => gps.GetPositionX()).
                Returns(1.1).
                Returns(1.5).
                Returns(1.8);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
        }

        [Test]
        public void Next_stub_should_return_consecutive_values()
        {
            IGPS gps = Mock.Create<IGPS>();

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1);
            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.5);
            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.8);


            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
        }

        [Test]
        public void Next_return_short_should_return_consecutive_values()
        {
            IGPS gps = Mock.Create<IGPS>();

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1, 1.5, 1.8);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
        }

        [Test]
        public void Should_callback_only_after_last_call()
        {
            IGPS gps = Mock.Create<IGPS>();
            int i = 0;

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1, 1.5, 1.8).Callback(() => i++);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(0, i);
            Assert.AreEqual(1.8, gps.GetPositionX());
            Assert.AreEqual(1, i);
        }

        [Test]
        public void Should_throw_exception_after_last_call()
        {
            Stack stack = Mock.Create<Stack>();

            Mock.WhenCalled(() => stack.Pop()).
                Returns(10).
                Throws(new InvalidOperationException());

            Assert.AreEqual(10, stack.Pop());
            Assert.Throws<InvalidOperationException>(() => stack.Pop());
        }

        [Test]
        public void Should_return_last_stubbed_value()
        {
            IGPS gps = Mock.Create<IGPS>();

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1, 1.5, 1.8);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
            
            // always return last..
            Assert.AreEqual(1.8, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
        }

        [Test]
        public void Should_verify_were_called_exact_times()
        {
            IGPS gps = Mock.Create<IGPS>();

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1, 1.5, 1.8);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());
            Mock.VerifyCalledWithAnyArgs(() => gps.GetPositionX(), Times.Exactly(3));
        }

        [Test]
        public void Should_raise_event_only_after_last_call()
        {
            var gps = Mock.Create<IGPS>();
            var screen1 = Mock.Create<Screen>();

            gps.PositionChanged += screen1.UpdatePosition;

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1, 1.5, 1.8)
                .Raises(() => gps.PositionChanged += null, EventArgs.Empty);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.5, gps.GetPositionX());
            Mock.VerifyNotCalled(() => screen1.UpdatePosition(Arg.Any<object>(), EventArgs.Empty));
            Assert.AreEqual(1.8, gps.GetPositionX());
            Mock.VerifyCalledWithAnyArgs(() => screen1.UpdatePosition(Arg.Any<object>(), EventArgs.Empty), Times.Exactly(1));
        }

        [Test]
        public void Should_not_raise_event_after_last_call()
        {
            var gps = Mock.Create<IGPS>();
            var screen1 = Mock.Create<Screen>();

            gps.PositionChanged += screen1.UpdatePosition;

            Mock.WhenCalled(() => gps.GetPositionX()).Returns(1.1)
                .Raises(() => gps.PositionChanged += null, EventArgs.Empty)
                .Returns(1.8);

            Assert.AreEqual(1.1, gps.GetPositionX());
            Assert.AreEqual(1.8, gps.GetPositionX());

            Mock.VerifyCalledWithAnyArgs(() => screen1.UpdatePosition(Arg.Any<object>(), EventArgs.Empty), Times.Exactly(1));
        } 
    }
}
